#include "shape.h"
#include <iostream>
#include <QFile>
#include <QTextStream>

using namespace std;
Shape::Shape():
    _position(QOpenGLBuffer::VertexBuffer),
    _color(QOpenGLBuffer::VertexBuffer),
    _normal(QOpenGLBuffer::VertexBuffer),
    _texcoord(QOpenGLBuffer::VertexBuffer),
    _index(QOpenGLBuffer::IndexBuffer)
{
    _trans_mat.setToIdentity();
}

Shape::~Shape(){

}

bool Shape::init(QOpenGLShaderProgram *_shader){

    _shader_program = _shader;

    prepare_buffer();
    _tex_vector.push_back(new GLTexture2D());
    _tex_vector[0]->create();

    _texmode = false;

    return true;
}

bool Shape::prepare_buffer(){
    _vao = new QOpenGLVertexArrayObject();
    _vao->create();
    _vao->bind();

    _position.create();
    _position.setUsagePattern(QOpenGLBuffer::DynamicDraw);

    _color.create();
    _color.setUsagePattern(QOpenGLBuffer::DynamicDraw);

    _normal.create();
    _normal.setUsagePattern(QOpenGLBuffer::DynamicDraw);

    _texcoord.create();
    _texcoord.setUsagePattern(QOpenGLBuffer::DynamicDraw);

    _index.create();
    _index.setUsagePattern(QOpenGLBuffer::DynamicDraw);


    return true;

}


void Shape::set_vertex_size(int n ){
    alloc_position(n*3);
    alloc_color(n*3);
    alloc_vnormal(n*3);
    alloc_texcoord(n*2);
}

void Shape::test_setup(int n){
}

void Shape::set_vertex_data(QVector<vtx_info> &_v){
    int n = _v.size();
    float* _pos_data = new float[n*3];
    float* _color_data = new float[n*3];
    float* _vnormal_data = new float[n*3];
    float* _tex_data = new float[n*2];

    for(int i=0;i<n;i++){
        _pos_data[i*3] = _v[i]._pos.x();
        _pos_data[i*3+1] = _v[i]._pos.y();
        _pos_data[i*3+2] = _v[i]._pos.z();

        _color_data[i*3] = _v[i]._color.x();
        _color_data[i*3+1] = _v[i]._color.y();
        _color_data[i*3+2] = _v[i]._color.z();

        _vnormal_data[i*3] = _v[i]._normal.x();
        _vnormal_data[i*3+1] = _v[i]._normal.y();
        _vnormal_data[i*3+2] = _v[i]._normal.z();

        _tex_data[i*2] = _v[i]._tex.x();
        _tex_data[i*2+1] = _v[i]._tex.y();
    }

    set_position_data(_pos_data, n*3);
    set_color_data(_color_data, n*3);
    set_vnormal_data(_vnormal_data, n*3);
    set_texcoord_data(_tex_data, n*2);

    delete _pos_data;
    delete _color_data;
    delete _vnormal_data;
    delete _tex_data;
}

void Shape::set_position(int _idx, QVector3D v){
    set_position(_idx, v.x(), v.y(), v.z());

}

void Shape::set_color(int _idx, QVector3D c){
    set_color(_idx, c.x(), c.y(), c.z());
}

void Shape::set_vnormal(int _idx, QVector3D v){
    set_vnormal(_idx, v.x(), v.y(), v.z());
}

void Shape::set_tex_info(int _idx, QVector2D uv){
    set_texcoord(_idx, uv.x(), uv.y());
}

void Shape::set_num_triangle(int n){
    _index.bind();
    _index.allocate(n*3*sizeof(int));
}


void Shape::set_triangle(int i, tri_info _t){
    _index.bind();
    int *_data = (int*) _index.map(QOpenGLBuffer::WriteOnly);
    _data[i*3] = _t._v1;
    _data[i*3+1] = _t._v2;
    _data[i*3+2] = _t._v3;
    _index.unmap();
}

void Shape::set_triangle_data(QVector<tri_info> & _tri_list){
    int n = _tri_list.size();
    int* _idx_array = new int[n*3];
    for(int i=0;i<n;i++){
        _idx_array[3*i] = _tri_list[i]._v1;
        _idx_array[3*i+1] = _tri_list[i]._v2;
        _idx_array[3*i+2] = _tri_list[i]._v3;
    }
    _index.bind();
    _index.allocate(_idx_array,sizeof(int)*n*3);
    delete _idx_array;
}

void Shape::enable_texture(bool b){
    _texmode = b;
}

bool Shape::load_texture(QString file){
    _tex_vector[0]->bind();
    return _tex_vector[0]->load(file);
}

void Shape::draw(){
    int _pj_loc,_mv_loc;

    _vao->bind();
    _shader_program->bind();

    _pj_loc = _shader_program->uniformLocation("proj_matrix");
    _mv_loc = _shader_program->uniformLocation("mv_matrix");
    _shader_program->setUniformValue(_pj_loc,_proj_mat);
    _shader_program->setUniformValue(_mv_loc,_model_mat*_trans_mat);

    _shader_program->setUniformValue("isTex",_texmode);

    //glActiveTexture(GL_TEXTURE0);
    _tex_vector[0]->bind();

    _position.bind();
    _shader_program->enableAttributeArray("VPos");
    _shader_program->setAttributeBuffer("VPos",GL_FLOAT,0,3);

    _color.bind();
    _shader_program->enableAttributeArray("VColor");
    _shader_program->setAttributeBuffer("VColor",GL_FLOAT,0,3);

    _normal.bind();
    _shader_program->enableAttributeArray("VNormal");
    _shader_program->setAttributeBuffer("VNormal",GL_FLOAT,0,3);

    _texcoord.bind();
    _shader_program->enableAttributeArray("TexUV");
    _shader_program->setAttributeBuffer("TexUV",GL_FLOAT,0,2);

    _index.bind();

    glDrawElements(GL_TRIANGLES,_index.size()/sizeof(int),GL_UNSIGNED_INT,NULL);
    //glDrawArrays(GL_TRIANGLES, 0, 3);
}


bool Shape::alloc_position(int _size){
    _vao->bind();
    _shader_program->bind();
    if(!_position.bind()){
        return false;
    }
    _position.allocate(_size *  sizeof(float));
    return true;
}


bool Shape::alloc_color(int _size){
    _vao->bind();
    _shader_program->bind();
    if(!_color.bind()){
        return false;
    }
    _color.allocate(_size *  sizeof(float));
    return true;
}

bool Shape::alloc_vnormal(int _size){
    _vao->bind();
    _shader_program->bind();
    if(!_normal.bind()){
        return false;
    }
    _normal.allocate(_size *  sizeof(float));
    return true;
}

bool Shape::alloc_texcoord(int _size){
    _vao->bind();
    _shader_program->bind();
    if(!_texcoord.bind()){
        return false;
    }
    _texcoord.allocate(_size *  sizeof(float));
    return true;
}

bool Shape::set_position_data(float *_data, int _size){
    _vao->bind();
    _shader_program->bind();
    if(!_position.bind()){
        return false;
    }
    _position.allocate(_data,_size * sizeof(float));

    return true;
}


bool Shape::set_color_data(float *_data, int _size){
    _vao->bind();
    _shader_program->bind();
    if(!_color.bind()){
        return false;
    }
    _color.allocate(_data, _size * sizeof(float));
    return true;
}
bool Shape::set_vnormal_data(float *_data, int _size){
    _vao->bind();
    _shader_program->bind();
    if(!_normal.bind()){
        return false;
    }
    _normal.allocate(_data, _size * sizeof(float));
    return true;
}


bool Shape::set_texcoord_data(float *_data, int _size){
    _vao->bind();
    _shader_program->bind();
    if(!_texcoord.bind()){
        return false;
    }
    _texcoord.allocate(_data, _size * sizeof(float));
    return true;
}


bool Shape::set_position(int _idx, float _x, float _y, float _z){
    _vao->bind();
    _shader_program->bind();
    _position.bind();
    if(_idx*3 > _position.size()) {
        return false;
    }

    float * _data = (float*) _position.map(QOpenGLBuffer::WriteOnly);
    if(!_data) return false;

    _data[3*_idx] = _x;
    _data[3*_idx + 1] = _y;
    _data[3*_idx + 2] = _z;

    if(!_position.unmap()) return false;
    return true;
}

bool Shape::set_color(int _idx, float _r, float _g, float _b){
    _vao->bind();
    _shader_program->bind();
    _color.bind();
    if(_idx*3 > _color.size()) {
        return false;
    }
    float * _data = (float*) _color.map(QOpenGLBuffer::WriteOnly);
    if(!_data) return false;

    _data[3*_idx] = _r;
    _data[3*_idx + 1] = _g;
    _data[3*_idx + 2] = _b;

    if(!_color.unmap()) return false;

    return true;
}

bool Shape::set_vnormal(int _idx, float _x, float _y, float _z){
    _vao->bind();
    _shader_program->bind();
    _normal.bind();
    if(_idx*3 > _normal.size()) {
        return false;
    }

    float * _data = (float*) _normal.map(QOpenGLBuffer::WriteOnly);
    if(!_data) return false;

    _data[3*_idx] = _x;
    _data[3*_idx + 1] = _y;
    _data[3*_idx + 2] = _z;

    if(!_normal.unmap()) return false;
    return true;
}


bool Shape::set_texcoord(int _idx, float _u, float _v){
    _vao->bind();
    _shader_program->bind();
    _texcoord.bind();
    if(_idx*2 > _texcoord.size()) {
        return false;
    }
    float * _data = (float*) _texcoord.map(QOpenGLBuffer::WriteOnly);
    if(!_data) return false;

    _data[2*_idx] = _u;
    _data[2*_idx + 1] = _v;

    if(!_texcoord.unmap()) return false;

    return true;
}

bool Shape::set_position_subdata(float *_data, int _offset, int _size){
    _vao->bind();
    _shader_program->bind();
    _position.bind();
    if(_offset + _size > _position.size()) {
        return false;
    }

    float * _dst = (float*) _position.map(QOpenGLBuffer::WriteOnly);
    if(!_dst) return false;

    memcpy(_dst + _offset, _data, _size);

    if(!_position.unmap()) return false;
    return true;
}

bool Shape::set_color_subdata(float *_data, int _offset, int _size){
    _vao->bind();
    _shader_program->bind();
    _color.bind();
    if(_offset + _size > _color.size()) {
        return false;
    }

    float * _dst = (float*) _color.map(QOpenGLBuffer::WriteOnly);
    if(!_dst) return false;

    memcpy(_dst + _offset, _data, _size);

    if(!_color.unmap()) return false;
    return true;
}

bool Shape::set_vnormal_subdata(float *_data, int _offset, int _size){
    _vao->bind();
    _shader_program->bind();
    _normal.bind();
    if(_offset + _size > _normal.size()) {
        return false;
    }

    float * _dst = (float*) _normal.map(QOpenGLBuffer::WriteOnly);
    if(!_dst) return false;

    memcpy(_dst + _offset, _data, _size);

    if(!_normal.unmap()) return false;
    return true;
}


bool Shape::set_texcoord_subdata(float *_data, int _offset, int _size){
    _vao->bind();
    _shader_program->bind();
    _texcoord.bind();
    if(_offset + _size > _texcoord.size()) {
        return false;
    }

    float * _dst = (float*) _texcoord.map(QOpenGLBuffer::WriteOnly);
    if(!_dst) return false;

    memcpy(_dst + _offset, _data, _size);

    if(!_texcoord.unmap()) return false;
    return true;
}

bool Shape::load_obj(QString filename){
    QFile f(filename);
    if(!f.open(QIODevice::ReadOnly|QIODevice::Text)){
        return false;
    }

    QTextStream in(&f);
    QVector<vtx_info> v_array;
    QVector<tri_info> t_array;
    int _vidx = 0;
    while(!in.atEnd()){
        QString line = in.readLine();
        QList<QString> tokens = line.split(' ');
        if(tokens[0]==QString("v")){
            vtx_info tempv;
            tempv._pos = QVector3D(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble());
            tempv._color = QVector3D(0.5,0.5,0.5);
            tempv._tex = QVector2D(0,0);
            v_array.push_back(tempv);
        }else if (tokens[0]==QString("vn")){
            v_array[_vidx]._normal = QVector3D(tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble());
            _vidx++;
        }else if(tokens[0]==QString("f")){
            tri_info tempt;
            tempt._v1 = tokens[1].left(tokens[1].indexOf('/')).toInt() - 1;
            tempt._v2 = tokens[2].left(tokens[2].indexOf('/')).toInt() - 1;
            tempt._v3 = tokens[3].left(tokens[3].indexOf('/')).toInt() - 1;
            t_array.push_back(tempt);
        }
    }




    set_vertex_data(v_array);
    set_triangle_data(t_array);



    return true;
}

void Shape::translate(QVector3D v){
    QMatrix4x4 temp;
    temp.translate(v);
    _trans_mat = temp * _trans_mat;
}

void Shape::scale(float v){
    QMatrix4x4 temp;
    temp.scale(v);
    _trans_mat = temp * _trans_mat;
}

void Shape::rotate(QVector3D v , float a){
    QMatrix4x4 temp;
    temp.rotate(a,v);
    _trans_mat = temp * _trans_mat;
}


